package com.ruoyi.activiti.controller;

import cn.hutool.core.collection.CollectionUtil;
import com.ruoyi.activiti.config.CustomProcessDiagramGenerator;
import com.ruoyi.activiti.config.WorkflowConstants;
import com.ruoyi.activiti.domain.MyHistoricTask;
import com.ruoyi.activiti.domain.MyProcessInstanceCondition;
import com.ruoyi.activiti.service.ActivitiConvertService;
import com.ruoyi.activiti.service.ProcessInstanceService;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.core.web.page.AjaxPageResult;
import com.ruoyi.common.core.web.page.PageDomain;
import com.ruoyi.common.core.web.page.TableSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.HistoryService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricProcessInstanceQuery;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author scihi
 */
@RestController
@RequestMapping("/activiti/process")
@Api(tags="流程实例")
@Validated//有此注解，才会校验游离的参数
public class ProcessInstanceController extends BaseController {

    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private HistoryService historyService;
    @Autowired
    private ProcessEngine processEngine;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private ActivitiConvertService activitiConvertService;
    @Autowired
    private ProcessInstanceService processService;

    @ApiOperation(value = "查询流程实例列表")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "category", value = "流程实例分组（也就是流程定义分组）", paramType = "form")
            ,@ApiImplicitParam(name = "procDefId", value = "流程定义id", paramType = "form")
            ,@ApiImplicitParam(name = "username", value = "参与人username", paramType = "form")
            ,@ApiImplicitParam(name = "startUserName", value = "发起人userName", paramType = "form")
            ,@ApiImplicitParam(name = "procDefKey", value = "流程定义key", paramType = "form")
            ,@ApiImplicitParam(name = "pageNum", value = "页码，从1开始，默认1", paramType = "form", defaultValue = "1")
            ,@ApiImplicitParam(name = "pageSize", value = "每页条数，默认20", paramType = "form", defaultValue = "20")
    })
    @GetMapping("/list")
    public AjaxPageResult list(String category, String procDefId, String username, String startUserName, String procDefKey) {
        PageDomain pageDomain = TableSupport.buildPageRequest();
        Integer pageNum = pageDomain.getPageNum();
        Integer pageSize = pageDomain.getPageSize();
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery()
                .orderByProcessInstanceStartTime().desc();
        if(StringUtils.isNotEmpty(category)){
            query.processDefinitionCategory(category);
        }
        if(StringUtils.isNotEmpty(procDefId)){
            query.processDefinitionId(procDefId);
        }
        if(StringUtils.isNotEmpty(procDefKey)){
            query.processDefinitionKey(procDefKey);
        }
        Set<String> ids;
        if(StringUtils.isNotEmpty(username) || StringUtils.isNotEmpty(startUserName)){
            MyProcessInstanceCondition condition = new MyProcessInstanceCondition();
            condition.setUsername(username);
            condition.setStartUserName(startUserName);
            ids = processService.listInstanceIds(condition);
            if(CollectionUtil.isNotEmpty(ids)){
                query.processInstanceIds(ids);
            }
        }
        List<HistoricProcessInstance> list = query.listPage((pageNum - 1) * pageSize, pageSize);
        return AjaxPageResult.success(activitiConvertService.convertProcessInstanceList(list, true))
                .setTotal(query.count())
                .setPageNum(pageNum)
                .setPageSize(pageSize);
    }

    @ApiOperation(value = "查询流程实例", notes = "只能查询流程中的实例")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "procInstId", value = "流程实例id", paramType = "path")
    })
    @GetMapping("/{procInstId}")
    @ResponseBody
    public AjaxResult single(@PathVariable String procInstId) {
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(procInstId);
        HistoricProcessInstance processInstance = query.singleResult();
        if(processInstance==null){
            return AjaxResult.error("数据不存在");
        }
        return AjaxResult.success(activitiConvertService.convertProcessInstance(processInstance, true));
    }

    @ApiOperation(value = "查询审批历史", notes = "")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "processInstanceId", value = "流程实例id", paramType = "form")
            ,@ApiImplicitParam(name = "pageNum", value = "页码，从1开始，默认1", paramType = "form", defaultValue = "1")
            ,@ApiImplicitParam(name = "pageSize", value = "每页条数，默认20", paramType = "form", defaultValue = "20")
    })
    @GetMapping("/listHistory")
    @ResponseBody
    public AjaxPageResult listHistory(String processInstanceId) {
        PageDomain pageDomain = TableSupport.buildPageRequest();
        Integer pageNum = pageDomain.getPageNum();
        Integer pageSize = pageDomain.getPageSize();
        List<MyHistoricTask> list = processService.selectHistoryList(processInstanceId, pageNum, pageSize);
        return AjaxPageResult.success(list).setTotal(processService.countHistoryList(processInstanceId));
    }

    @ApiOperation(value = "查看流程图", notes = "")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "procInstId", value = "流程实例id", paramType = "path")
    })
    @GetMapping(value = "/diagram/inst/{procInstId}")
    public void readResource(@PathVariable("procInstId") String procInstId, HttpServletResponse response) throws Exception {
        // 设置页面不缓存
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        String processDefinitionId = "";
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(procInstId).singleResult();
        if (processInstance == null) {
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(procInstId).singleResult();
            processDefinitionId = historicProcessInstance.getProcessDefinitionId();
        } else {
            processDefinitionId = processInstance.getProcessDefinitionId();
        }
        ProcessDefinitionQuery pdq = repositoryService.createProcessDefinitionQuery();
        ProcessDefinition pd = pdq.processDefinitionId(processDefinitionId).singleResult();

        String resourceName = pd.getDiagramResourceName();

        if (resourceName.endsWith(".png") && !StringUtils.isEmpty(procInstId)) {
            getActivitiProccessImage(procInstId, response);
            //ProcessDiagramGenerator.generateDiagram(pde, "png", getRuntimeService().getActiveActivityIds(processInstanceId));
        } else {
            // 通过接口读取
            InputStream resourceAsStream = repositoryService.getResourceAsStream(pd.getDeploymentId(), resourceName);

            // 输出资源内容到相应对象
            byte[] b = new byte[1024];
            int len = -1;
            while ((len = resourceAsStream.read(b, 0, 1024)) != -1) {
                response.getOutputStream().write(b, 0, len);
            }
        }
    }

    /**
     * 获取流程图像，已执行节点和流程线高亮显示
     */
    private void getActivitiProccessImage(String pProcessInstanceId, HttpServletResponse response) {
        //logger.info("[开始]-获取流程图图像");
        try {
            //  获取历史流程实例
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(pProcessInstanceId).singleResult();

            if (historicProcessInstance == null) {
                //throw new BusinessException("获取流程实例ID[" + pProcessInstanceId + "]对应的历史流程实例失败！");
            } else {
                // 获取流程定义
                ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
                        .getDeployedProcessDefinition(historicProcessInstance.getProcessDefinitionId());

                // 获取流程历史中已执行节点，并按照节点在流程中执行先后顺序排序
                List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
                        .processInstanceId(pProcessInstanceId).orderByHistoricActivityInstanceId().asc().list();

                // 已执行的节点ID集合
                List<String> executedActivityIdList = new ArrayList<String>();
                int index = 1;
                //logger.info("获取已经执行的节点ID");
                for (HistoricActivityInstance activityInstance : historicActivityInstanceList) {
                    executedActivityIdList.add(activityInstance.getActivityId());

                    //logger.info("第[" + index + "]个已执行节点=" + activityInstance.getActivityId() + " : " +activityInstance.getActivityName());
                    index++;
                }

                BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());

                // 已执行的线集合
                List<String> flowIds = new ArrayList<String>();
                // 获取流程走过的线 (getHighLightedFlows是下面的方法)
                flowIds = getHighLightedFlows(bpmnModel, processDefinition, historicActivityInstanceList);

//                // 获取流程图图像字符流
//                ProcessDiagramGenerator pec = processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
//                //配置字体
//                InputStream imageStream = pec.generateDiagram(bpmnModel, "png", executedActivityIdList, flowIds,"宋体","微软雅黑","黑体",null,2.0);

                Set<String> currIds = runtimeService.createExecutionQuery().processInstanceId(pProcessInstanceId).list()
                        .stream().map(e -> e.getActivityId()).collect(Collectors.toSet());

                CustomProcessDiagramGenerator diagramGenerator = (CustomProcessDiagramGenerator) processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
                InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel, "png", executedActivityIdList,
                        flowIds, "宋体", "宋体", "宋体", null, 1.0, new Color[]{WorkflowConstants.COLOR_NORMAL, WorkflowConstants.COLOR_CURRENT}, currIds);

                response.setContentType("image/png");
                OutputStream os = response.getOutputStream();
                int bytesRead = 0;
                byte[] buffer = new byte[8192];
                while ((bytesRead = imageStream.read(buffer, 0, 8192)) != -1) {
                    os.write(buffer, 0, bytesRead);
                }
                os.close();
                imageStream.close();
            }
            //logger.info("[完成]-获取流程图图像");
        } catch (Exception e) {
            System.out.println(e.getMessage());
            //logger.error("【异常】-获取流程图失败！" + e.getMessage());
            //throw new BusinessException("获取流程图失败！" + e.getMessage());
        }
    }

    private List<String> getHighLightedFlows(BpmnModel bpmnModel, ProcessDefinitionEntity processDefinitionEntity, List<HistoricActivityInstance> historicActivityInstances) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //24小时制
        List<String> highFlows = new ArrayList<String>();// 用以保存高亮的线flowId

        for (int i = 0; i < historicActivityInstances.size() - 1; i++) {
            // 对历史流程节点进行遍历
            // 得到节点定义的详细信息
            FlowNode activityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i).getActivityId());


            List<FlowNode> sameStartTimeNodes = new ArrayList<FlowNode>();// 用以保存后续开始时间相同的节点
            FlowNode sameActivityImpl1 = null;

            HistoricActivityInstance activityImpl_ = historicActivityInstances.get(i);// 第一个节点
            HistoricActivityInstance activityImp2_;

            for (int k = i + 1; k <= historicActivityInstances.size() - 1; k++) {
                activityImp2_ = historicActivityInstances.get(k);// 后续第1个节点

                if (activityImpl_.getActivityType().equals("userTask") && activityImp2_.getActivityType().equals("userTask") &&
                        df.format(activityImpl_.getStartTime()).equals(df.format(activityImp2_.getStartTime()))){ //都是usertask，且主节点与后续节点的开始时间相同，说明不是真实的后继节点

                } else {
                    sameActivityImpl1 = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(k).getActivityId());//找到紧跟在后面的一个节点
                    break;
                }

            }
            sameStartTimeNodes.add(sameActivityImpl1); // 将后面第一个节点放在时间相同节点的集合里
            for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) {
                HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j);// 后续第一个节点
                HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1);// 后续第二个节点

                if (df.format(activityImpl1.getStartTime()).equals(df.format(activityImpl2.getStartTime()))) {// 如果第一个节点和第二个节点开始时间相同保存
                    FlowNode sameActivityImpl2 = (FlowNode) bpmnModel.getMainProcess().getFlowElement(activityImpl2.getActivityId());
                    sameStartTimeNodes.add(sameActivityImpl2);
                } else {// 有不相同跳出循环
                    break;
                }
            }
            List<SequenceFlow> pvmTransitions = activityImpl.getOutgoingFlows(); // 取出节点的所有出去的线

            for (SequenceFlow pvmTransition : pvmTransitions) {// 对所有的线进行遍历
                FlowNode pvmActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(pvmTransition.getTargetRef());// 如果取出的线的目标节点存在时间相同的节点里，保存该线的id，进行高亮显示
                if (sameStartTimeNodes.contains(pvmActivityImpl)) {
                    highFlows.add(pvmTransition.getId());
                }
            }

        }
        return highFlows;

    }

    @ApiOperation(value = "撤销流程实例", notes = "")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "instanceId", value = "流程实例id", paramType = "form")
    })
    @PostMapping("/cancelApply")
    @ResponseBody
    public AjaxResult cancelApply(String instanceId) {
        processService.cancelApply(instanceId, "用户撤销");
        return success();
    }

    @ApiOperation(value = "删除流程实例", notes = "")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "procInstId", value = "流程实例id", paramType = "path")
    })
    @DeleteMapping("/{procInstId}")
    @ResponseBody
    public AjaxResult delete(@PathVariable String procInstId) {
        runtimeService.deleteProcessInstance(procInstId, "系统删除");
        return AjaxResult.success();
    }

    /**
     * 激活/挂起流程实例
     */
    @PostMapping("/suspendOrActiveApply")
    @ResponseBody
    public AjaxResult suspendOrActiveApply(String instanceId, String suspendState) {
        processService.suspendOrActiveApply(instanceId, suspendState);
        return success();
    }
}
